/*     */ package com.zimbra.cs.mailbox;
/*     */ 
/*     */ import com.google.common.base.Objects;
/*     */ import com.google.common.base.Objects.ToStringHelper;
/*     */ import com.google.common.base.Strings;
/*     */ import com.google.common.collect.Lists;
/*     */ import com.zimbra.common.account.ZAttrProvisioning.MailThreadingAlgorithm;
/*     */ import com.zimbra.common.localconfig.DebugConfig;
/*     */ import com.zimbra.common.mime.HeaderUtils;
/*     */ import com.zimbra.common.mime.MimeHeader;
/*     */ import com.zimbra.common.service.ServiceException;
/*     */ import com.zimbra.common.util.ByteUtil;
/*     */ import com.zimbra.common.util.Log;
/*     */ import com.zimbra.common.util.ZimbraLog;
/*     */ import com.zimbra.cs.account.Account;
/*     */ import com.zimbra.cs.db.DbMailItem;
/*     */ import com.zimbra.cs.mime.Mime;
/*     */ import com.zimbra.cs.mime.ParsedMessage;
/*     */ import java.security.MessageDigest;
/*     */ import java.security.NoSuchAlgorithmException;
/*     */ import java.util.ArrayList;
/*     */ import java.util.Collection;
/*     */ import java.util.Collections;
/*     */ import java.util.List;
/*     */ import java.util.Random;
/*     */ import javax.mail.internet.MimeMessage;
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ public final class Threader
/*     */ {
/*     */   private final ZAttrProvisioning.MailThreadingAlgorithm mode;
/*     */   private final Mailbox mbox;
/*     */   private final ParsedMessage pm;
/*     */   private final String subjHash;
/*     */   private List<String> refHashes;
/*     */   private List<Conversation> matchedConversations;
/*     */   static final String IGNORE_THREAD_INDEX = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/*     */   private static final long CONVERSATION_REPLY_WINDOW = 2678400000L;
/*     */   private static final long CONVERSATION_NONREPLY_WINDOW = 172800000L;
/*     */   private static final int CONVERSATION_NONREPLY_SIZE_LIMIT = 50;
/*     */   
/*     */   public Threader(Mailbox mbox, ParsedMessage pm)
/*     */     throws ServiceException
/*     */   {
/*  87 */     this.mbox = mbox;
/*  88 */     this.pm = pm;
/*  89 */     this.mode = getThreadingAlgorithm(mbox.getAccount());
/*  90 */     this.subjHash = (isEnabled() ? Mailbox.getHash(pm.getNormalizedSubject()) : null);
/*  91 */     this.refHashes = ((isEnabled()) && (!this.mode.isSubject()) ? getReferenceHashes(true) : null);
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */   private static ZAttrProvisioning.MailThreadingAlgorithm getThreadingAlgorithm(Account acct)
/*     */   {
/* 103 */     if (DebugConfig.disableConversation) {
/* 104 */       return ZAttrProvisioning.MailThreadingAlgorithm.none;
/*     */     }
/*     */     
/* 107 */     ZAttrProvisioning.MailThreadingAlgorithm tmode = acct.getMailThreadingAlgorithm();
/* 108 */     return tmode == null ? ZAttrProvisioning.MailThreadingAlgorithm.references : tmode;
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */   private List<String> getReferenceHashes(boolean includeParents)
/*     */   {
/* 129 */     List<String> hashes = new ArrayList();
/*     */     
/* 131 */     Collection<String> references = includeParents ? this.pm.getAllReferences() : Mime.getReferences(this.pm.getMimeMessage(), "Message-ID");
/* 132 */     for (String reference : references) {
/* 133 */       hashes.add(Mailbox.getHash("\001\002" + Strings.nullToEmpty(reference)));
/*     */     }
/*     */     
/*     */ 
/* 137 */     if (!this.mode.isStrict()) {
/* 138 */       hashes.addAll(getThreadIndexHashes(true));
/*     */     }
/*     */     
/* 141 */     return hashes;
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */   private List<String> getThreadIndexHashes(boolean includeParents)
/*     */   {
/* 156 */     byte[] tindex = null;
/*     */     try {
/* 158 */       String header = this.pm.getMimeMessage().getHeader("Thread-Index", null);
/* 159 */       if ((header != null) && (header.startsWith("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")))
/*     */       {
/* 161 */         return Collections.emptyList();
/*     */       }
/* 163 */       tindex = ThreadIndex.parseHeader(header);
/*     */     } catch (Exception e) {}
/* 165 */     if (tindex == null) {
/* 166 */       return Collections.emptyList();
/*     */     }
/* 168 */     List<String> hashes = new ArrayList(3);
/*     */     
/* 170 */     int length = tindex.length;
/*     */     do {
/*     */       try {
/* 173 */         MessageDigest md = MessageDigest.getInstance("SHA1");
/* 174 */         md.update((byte)1);md.update((byte)3);
/* 175 */         md.update(tindex, 0, length);
/* 176 */         hashes.add(ByteUtil.encodeFSSafeBase64(md.digest()));
/*     */       } catch (NoSuchAlgorithmException e) {
/* 178 */         return Collections.emptyList();
/*     */       }
/* 180 */       length -= 5;
/* 181 */     } while ((length >= 22) && (includeParents));
/* 182 */     return hashes;
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */   public static class ThreadIndex
/*     */   {
/*     */     public static String newThreadIndex()
/*     */     {
/* 191 */       byte[] random = new byte[22];
/* 192 */       new Random().nextBytes(random);
/* 193 */       return HeaderUtils.encodeB2047(random).trim();
/*     */     }
/*     */     
/*     */ 
/*     */ 
/*     */ 
/*     */     public static String addChild(byte[] oldTIndex)
/*     */     {
/* 201 */       if (oldTIndex == null) {
/* 202 */         return null;
/*     */       }
/*     */       
/* 205 */       int oldLength = oldTIndex.length;
/* 206 */       byte[] tindex = new byte[oldLength + 5];byte[] random = new byte[5];
/* 207 */       new Random().nextBytes(random);
/* 208 */       System.arraycopy(oldTIndex, 0, tindex, 0, oldLength);
/* 209 */       System.arraycopy(random, 0, tindex, oldLength, 5);
/* 210 */       return HeaderUtils.encodeB2047(tindex).trim();
/*     */     }
/*     */     
/*     */ 
/*     */ 
/*     */ 
/*     */     public static byte[] parseHeader(String tidxHdr)
/*     */     {
/* 218 */       if ((tidxHdr == null) || (tidxHdr.trim().isEmpty())) {
/* 219 */         return null;
/*     */       }
/*     */       
/* 222 */       byte[] tindex = HeaderUtils.decodeB2047(tidxHdr.trim());
/* 223 */       if (tindex.length % 5 != 2) {
/* 224 */         ZimbraLog.mailbox.debug("  ignoring Thread-Index of decoded length %d", new Object[] { Integer.valueOf(tindex.length) });
/* 225 */         return null;
/*     */       }
/* 227 */       return tindex;
/*     */     }
/*     */     
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */     public static String newThreadTopic(String subject)
/*     */     {
/* 237 */       String ttopic = ParsedMessage.normalize(subject);
/* 238 */       return ttopic == null ? null : MimeHeader.escape(ttopic, null, false);
/*     */     }
/*     */   }
/*     */   
/*     */ 
/*     */   boolean isEnabled()
/*     */   {
/* 245 */     return !this.mode.isNone();
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */   static boolean isHashPurgeAllowed(Account acct)
/*     */   {
/* 253 */     ZAttrProvisioning.MailThreadingAlgorithm tmode = getThreadingAlgorithm(acct);
/* 254 */     return (tmode.isNone()) || (tmode.isSubject());
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */   List<Conversation> lookupConversation()
/*     */     throws ServiceException
/*     */   {
/* 266 */     if (this.matchedConversations == null) {
/* 267 */       if (this.mode.isNone()) {
/* 268 */         return Collections.emptyList();
/*     */       }
/* 270 */       ZimbraLog.mailbox.debug("  threading message \"%s\" (%s)", new Object[] { this.pm.getSubject(), this.pm.getMessageID() });
/*     */       
/* 272 */       List<Conversation> matches = Collections.emptyList();
/* 273 */       if (!this.mode.isSubject())
/*     */       {
/* 275 */         matches = lookupByReference();
/*     */       }
/* 277 */       if ((matches.isEmpty()) && ((this.mode.isSubject()) || ((!this.mode.isStrict()) && (isReplyWithoutReferences()))))
/*     */       {
/* 279 */         matches = lookupBySubject();
/*     */       }
/* 281 */       this.matchedConversations = matches;
/*     */     }
/* 283 */     return new ArrayList(this.matchedConversations);
/*     */   }
/*     */   
/*     */ 
/*     */   void reset()
/*     */   {
/* 289 */     this.matchedConversations = null;
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */   private boolean isReplyWithoutReferences()
/*     */   {
/* 297 */     if (!this.pm.isReply()) {
/* 298 */       return false;
/*     */     }
/*     */     
/* 301 */     MimeMessage mm = this.pm.getMimeMessage();
/* 302 */     return (Mime.getReferences(mm, "In-Reply-To").isEmpty()) && (Mime.getReferences(mm, "References").isEmpty());
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */   private List<Conversation> lookupByReference()
/*     */     throws ServiceException
/*     */   {
/* 315 */     if ((this.refHashes == null) || (this.refHashes.isEmpty())) {
/* 316 */       return Collections.emptyList();
/*     */     }
/* 318 */     ZimbraLog.mailbox.debug("  lookup by references (%s): %s", new Object[] { this.mode, this.refHashes });
/* 319 */     List<MailItem.UnderlyingData> dlist = DbMailItem.getByHashes(this.mbox, this.refHashes);
/* 320 */     if ((dlist == null) || (dlist.isEmpty())) {
/* 321 */       ZimbraLog.mailbox.debug("  no reference matches found");
/* 322 */       return Collections.emptyList();
/*     */     }
/*     */     
/* 325 */     List<Conversation> matches = new ArrayList(dlist.size());
/* 326 */     for (MailItem.UnderlyingData data : dlist) {
/* 327 */       if (data.type == MailItem.Type.CONVERSATION.toByte()) {
/* 328 */         matches.add(this.mbox.getConversation(data));
/*     */       } else {
/* 330 */         matches.add((Conversation)this.mbox.getMessage(data).getParent());
/*     */       }
/*     */     }
/* 333 */     ZimbraLog.mailbox.debug("  found %d reference match(es)", new Object[] { Integer.valueOf(matches.size()) });
/*     */     
/* 335 */     if (this.mode.isSubjrefs())
/*     */     {
/* 337 */       for (int i = matches.size() - 1; i >= 0; i--) {
/* 338 */         Conversation hit = (Conversation)matches.get(i);
/* 339 */         if (!this.pm.getNormalizedSubject().equals(hit.getNormalizedSubject())) {
/* 340 */           ZimbraLog.mailbox.debug("  dropping one reference match due to non-matching subjects");
/* 341 */           matches.remove(i);
/*     */           
/*     */ 
/* 344 */           this.refHashes = getReferenceHashes(false);
/*     */         }
/*     */       }
/* 347 */       if (matches.isEmpty()) {
/* 348 */         ZimbraLog.mailbox.debug("  no valid reference matches found");
/*     */       }
/*     */     }
/* 351 */     assert (matches != null);
/* 352 */     return matches;
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */   private List<Conversation> lookupBySubject()
/*     */     throws ServiceException
/*     */   {
/* 379 */     if (this.subjHash == null) {
/* 380 */       return null;
/*     */     }
/*     */     
/* 383 */     ZimbraLog.mailbox.debug("  lookup by subject (%s): %s", new Object[] { this.mode, this.pm.getNormalizedSubject() });
/* 384 */     Conversation conv = this.mbox.getConversationByHash(this.subjHash);
/* 385 */     if (conv == null) {
/* 386 */       ZimbraLog.mailbox.debug("  no subject matches found");
/* 387 */       return Collections.emptyList();
/*     */     }
/*     */     
/* 390 */     ZimbraLog.mailbox.debug("  found conversation %d for subject hash: %s", new Object[] { Integer.valueOf(conv.getId()), this.subjHash });
/*     */     
/*     */ 
/* 393 */     long window = this.pm.isReply() ? 2678400000L : 172800000L;
/* 394 */     if (this.pm.getReceivedDate() > conv.getDate() + window)
/*     */     {
/* 396 */       ZimbraLog.mailbox.debug("  but rejected it because it's too old");
/* 397 */       return Collections.emptyList();
/*     */     }
/*     */     
/* 400 */     if ((!this.pm.isReply()) && (conv.getSize() > 50L))
/*     */     {
/* 402 */       ZimbraLog.mailbox.debug("  but rejected it because it's too big to add a non-reply");
/* 403 */       return Collections.emptyList();
/*     */     }
/*     */     
/* 406 */     return Lists.newArrayList(new Conversation[] { conv });
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */   void recordAddedMessage(Conversation conv)
/*     */     throws ServiceException
/*     */   {
/* 419 */     if (conv == null) {
/* 420 */       return;
/*     */     }
/* 422 */     if (this.subjHash != null)
/*     */     {
/* 424 */       this.mbox.openConversation(conv, this.subjHash);
/*     */     }
/*     */     
/* 427 */     if (this.refHashes != null) {
/* 428 */       for (String refHash : this.refHashes) {
/* 429 */         conv.open(refHash);
/*     */       }
/*     */     }
/*     */   }
/*     */   
/*     */ 
/*     */   void changeThreadingTargets(Message msg, Conversation conv)
/*     */     throws ServiceException
/*     */   {
/* 438 */     if ((conv != null) && (msg != null) && (isEnabled()) && (!this.mode.isSubject())) {
/* 439 */       DbMailItem.changeOpenTargets(msg, conv.getId());
/* 440 */       ZimbraLog.mailbox.debug("  transferred hashes from message %d to conv %d", new Object[] { Integer.valueOf(msg.getId()), Integer.valueOf(conv.getId()) });
/*     */     }
/*     */   }
/*     */   
/*     */   public String toString()
/*     */   {
/* 446 */     Objects.ToStringHelper tostr = Objects.toStringHelper(this);
/* 447 */     tostr.add("mode", this.mode);
/* 448 */     if (this.subjHash != null) {
/* 449 */       tostr.add("subjHash", this.subjHash);
/*     */     }
/* 451 */     if (this.refHashes != null) {
/* 452 */       tostr.add("refHashes", this.refHashes);
/*     */     }
/* 454 */     return tostr.toString();
/*     */   }
/*     */ }


/* Location:              /home/mint/zimbrastore.jar!/com/zimbra/cs/mailbox/Threader.class
 * Java compiler version: 7 (51.0)
 * JD-Core Version:       0.7.1
 */